<script setup lang="ts">
import Key from "./components/icons/Key.vue";
import Treasure from "./components/icons/Treasure.vue";
import Wall from "./components/icons/Wall.vue";
import Heart from "./components/icons/Heart.vue";
import Prompt from "./Prompt.vue";
import * as chars from "./chars";
import { Game, LevelResult } from "./game";
import { Princess } from "./princess";
import { computed, onMounted, reactive, ref } from "vue";
import { AteRecord } from "./ateRecord";
import { Coord } from "./coord";

const kindLookup: Record<string, string> = {
  [chars.wall]: "wall",
  [chars.joker]: "joker",
  [chars.road]: "road",
  [chars.princess]: "princess",
  [chars.monster]: "monster",
  [chars.question]: "question",
  [chars.puzzle]: "question",
  [chars.treasure]: "treasure",
  [chars.key]: "key",
  [chars.heart]: "heart",
};

let initialHealth = 10;
const princess = reactive(new Princess(initialHealth));

loadConfig("health.txt").then((text) => {
  const health = parseInt(text);
  if (health) {
    princess.health = initialHealth = health;
  }
});

const maze = reactive<string[][]>([]);

async function fetchMaze(level: number) {
  const rawMaze = await loadConfig(`maze${level}.txt`);
  return loadMaze(rawMaze);
}

const game = reactive(new Game());

game.next();
fetchMaze(game.level);

const help = reactive<string[]>([]);

loadConfig("help.txt").then((h) => {
  const rules = h
    .split("\n")
    .map((line) => line.trim())
    .filter((s) => s);
  help.push(...rules);
});

const keymap: Record<
  string,
  () => Partial<{ destChar: string; row: number; col: number }>
> = {
  w: up,
  a: left,
  s: down,
  d: right,
  arrowup: up,
  arrowleft: left,
  arrowdown: down,
  arrowright: right,
  k: up,
  h: left,
  j: down,
  l: right,
};

const ateTreasurePos = new Coord();

const questionVisible = ref(false);
const treasureVisible = ref(false);

document.body.addEventListener("keydown", (e) => {
  if (questionVisible.value || treasureVisible.value || game.levelResult) {
    return;
  }
  const key = e.key.toLowerCase();
  const action = keymap[key];
  if (!action) {
    return;
  }
  const { destChar, row, col } = action();
  const coord = new Coord(row, col);
  switch (destChar) {
    case chars.monster:
      if (hasMoreThings()) {
        game.moreThings();
      } else {
        game.pass();
        AteRecord.clear();
      }
      break;
    case chars.key:
    // fall-through
    case chars.treasure:
      if (princess.treasures === 2) {
        AteRecord.append(ateTreasurePos, coord);
        treasureVisible.value = true;
      } else {
        ateTreasurePos.row = row!;
        ateTreasurePos.col = col!;
      }
      break;
    case chars.question:
      questionVisible.value = true;
      console.log(row, col);
      AteRecord.append(coord);
      break;
    case chars.puzzle:
      questionVisible.value = true;
      console.log(row, col);
      break;
  }
});

async function loadConfig(path: string) {
  const r = await fetch(path);
  return r.text();
}

function up() {
  const { row, col } = princess.pos;
  if (row === 0 || chars.isWall(maze[row - 1][col])) {
    return {};
  }
  if (princess.health <= 0) {
    game.fail();
    return {};
  }
  const char = maze[row - 1][col];
  princess.visit(char);
  maze[row][col] = chars.road;
  maze[row - 1][col] = chars.princess;
  princess.pos.row--;
  return { destChar: char, row: row - 1, col };
}

function down() {
  const { row, col } = princess.pos;
  if (row === maze.length - 1 || chars.isWall(maze[row + 1][col])) {
    return {};
  }
  if (princess.health <= 0) {
    game.fail();
    return {};
  }
  const char = maze[row + 1][col];
  princess.visit(char);
  maze[row][col] = chars.road;
  maze[row + 1][col] = chars.princess;
  princess.pos.row++;
  return { destChar: char, row: row + 1, col };
}

function left() {
  const { row, col } = princess.pos;
  if (col === 0 || chars.isWall(maze[row][col - 1])) {
    return {};
  }
  if (princess.health <= 0) {
    game.fail();
    return {};
  }
  const char = maze[row][col - 1];
  princess.visit(char);
  maze[row][col] = chars.road;
  maze[row][col - 1] = chars.princess;
  princess.pos.col--;
  return { destChar: char, row, col: col - 1 };
}

function right() {
  const { row, col } = princess.pos;
  if (col === maze[0].length - 1 || chars.isWall(maze[row][col + 1])) {
    return {};
  }
  if (princess.health <= 0) {
    game.fail();
    return {};
  }
  const char = maze[row][col + 1];
  princess.visit(char);
  maze[row][col] = chars.road;
  maze[row][col + 1] = chars.princess;
  princess.pos.col++;
  return { destChar: char, row, col: col + 1 };
}

function loadMaze(rawMaze: string) {
  const cells = rawMaze
    .split("\n")
    .filter((s) => s)
    .map((s) => s.trim().split(""));

  maze.splice(0, maze.length, ...cells);

  for (const { row, col } of AteRecord.coordList) {
    maze[row][col] = chars.road;
  }

  for (let i = 0; i < cells.length; i++) {
    const row = cells[i];
    const j = row.indexOf("P");
    if (j >= 0) {
      princess.pos.row = i;
      princess.pos.col = j;
      break;
    }
  }
  princess.health = initialHealth;
}

const levelVisible = computed(() => game.levelResult !== LevelResult.pending);

function hasMoreThings() {
  return maze.some((row) =>
    row.some(
      (c) => c === chars.key || c === chars.treasure || c === chars.question,
    ),
  );
}

function next() {
  game.next();
  princess.treasures = 0;
  fetchMaze(game.level);
}

function restart() {
  game.restart();
  princess.treasures = 0;
  console.log("restart");
  fetchMaze(game.level);
}

const bgm = ref(null);

onMounted(() => {
  // 尝试自动播放
  bgm.value
    .play()
    .then(() => {
      console.log("BGM is playing");
    })
    .catch((error) => {
      console.log("BGM play failed:", error);
      // 处理自动播放失败的情况
      // 例如，可以显示一个播放按钮让用户手动播放
    });

  // 添加事件监听器，以便在用户交互后尝试播放
  document.body.addEventListener("click", function () {
    if (!bgm.paused) return;
    bgm.play().catch((error) => {
      console.log("BGM play failed after user interaction:", error);
    });
  });

  document.body.addEventListener("keydown", function () {
    if (!bgm.paused) return;
    bgm.play().catch((error) => {
      console.log("BGM play failed after user interaction:", error);
    });
  });

  // 可以添加更多用户交互事件监听器
});
</script>

<template>
  <audio ref="bgm" loop>
    <source src="/bgm.mp4" type="audio/mpeg" />
    Your browser does not support the audio element.
  </audio>

  <main>
    <section class="left">
      <h3>游戏规则</h3>
      <ol class="help">
        <li v-for="rule in help">{{ rule }}</li>
      </ol>
    </section>

    <table class="maze">
      <tr v-for="row in maze">
        <td v-for="cell in row" class="cell" :class="kindLookup[cell]">
          <Heart v-if="cell === chars.heart" />
          <Key v-else-if="cell === chars.key" />
          <Treasure v-else-if="cell === chars.treasure" />
          <Wall v-else-if="cell === chars.wall" />
          <Wall
            v-else-if="cell === chars.joker"
            @click="maze[8][18] = chars.heart"
          />
          <span v-else-if="cell === chars.question"> ? </span>
          <span v-else-if="cell === chars.puzzle"> ? </span>
          <img src="/princess.png" v-else-if="cell === chars.princess" />
          <img
            src="/monster.jpg"
            v-else-if="cell === chars.monster"
            class="monster"
          />
        </td>
      </tr>
    </table>

    <section class="right">
      <p>剩余体力：{{ princess.health }}</p>
      <p>关卡 {{ game.level }}/{{ Game.levelCount }}</p>
    </section>

    <Prompt
      v-model="questionVisible"
      :msg="game.getQuestion().msg"
      :level="game.level"
      @restart="restart"
    />

    <Prompt
      v-model="treasureVisible"
      :msg="game.getTreasureMessage()"
      :level="1"
    />

    <el-dialog
      v-model="levelVisible"
      :title="game.getMessage()"
      width="400"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      :show-close="false"
    >
      <div class="button-group">
        <el-button
          v-if="game.levelResult !== LevelResult.pass"
          type="primary"
          @click="restart"
          class="confirm"
        >
          重开
        </el-button>

        <el-button
          v-if="game.levelResult !== LevelResult.fail && game.hasNext()"
          type="primary"
          @click="next"
          class="confirm"
        >
          下一关
        </el-button>
        <el-button
          v-else-if="game.levelResult !== LevelResult.fail && !game.hasNext()"
          type="primary"
          @click="game.levelResult = LevelResult.pending"
          class="confirm"
        >
          好的
        </el-button>
      </div>
    </el-dialog>
  </main>
</template>

<style>
#app {
  font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
}

ul,
ol {
  padding: 0;
}
</style>

<style scoped>
main {
  display: flex;
  padding-top: 50px;
}

.left {
  width: 20%;
  padding: 0 10px 0 30px;
}

.left li {
  margin-top: 1em;
}

.maze {
  user-select: none;
}

.cell {
  width: 50px;
  height: 50px;
}

.wall,
.joker {
  background-color: #ccc;
}

.road,
.question {
  background-color: #eee;
}

.question {
  color: #fb6;
  font-weight: bold;
  font-size: 40px;
  line-height: 48px;
  text-align: center;
  text-shadow: #a96 2px 0 4px;
}

.monster {
  height: 48px;
}

.right {
  padding: 0 20px;
}

.button-group {
  display: flex;
}

.confirm {
  flex: 1;
}
</style>
